////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Julien Etienne.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
package org.siam.impl {
	
	import org.siam.core.ISiamManager;
	import org.siam.core.data.formatting.IFormattingManager;
	import org.siam.core.data.rendering.IRenderingManager;
	import org.siam.core.schemas.ISchema;
	import org.siam.core.schemas.ISchemaManager;
	import org.siam.core.schemas.ISchemaProperty;
	import org.siam.core.schemas.ISchemaRegistry;
	import org.siam.impl.data.formatting.FormattingManager;
	import org.siam.impl.data.formatting.FormattingManagerFactory;
	import org.siam.impl.data.rendering.RenderingManager;
	import org.siam.impl.data.rendering.RenderingManagerFactory;
	import org.siam.impl.schemas.SchemaFactory;
	import org.siam.impl.schemas.SchemaManager;
	import org.siam.impl.schemas.SchemaRegistry;
	import org.siam.utils.RuntimeError;
	import org.siam.utils.StringUtils;
	
	/**
	* 
	*
	**/
	[DefaultProperty(name="configuration")]
	public class SiamManager implements ISiamManager {
		
		private var _schemaRegistry:ISchemaRegistry;
		private var _formattingManager:IFormattingManager;
		private var _renderingManager:IRenderingManager;
		
		/**
		* 
		*
		**/
		public function SiamManager() {
			_schemaRegistry = new SchemaRegistry();
			_formattingManager = new FormattingManager();
			_renderingManager = new RenderingManager();
		}

		[Inspectable(category="General")]
		public function set configuration(value:XML):void {
			if (value == null) throw new RuntimeError("XML Configuration is missing!");
			
			parseXML(value);
		}
		
		/**
		* 
		*
		**/
		public function manage(schemaId:String):ISchemaManager {
			var schema:ISchema = _schemaRegistry.find(schemaId);
			return new SchemaManager(this, schema);
		}
		
		/**
		* 
		*
		**/
		public function get schemaIds():Array {
			return _schemaRegistry.ids;
		}
		
		/**
		* 
		*
		**/
		public function get formatting():IFormattingManager {
			return _formattingManager;			
		}
		
		/**
		* 
		*
		**/
		public function get rendering():IRenderingManager {
			return _renderingManager;			
		}
		
		internal function hasSchema(id:String):Boolean {
			return _schemaRegistry.has(id);
		}
		
		internal function registerSchema(id:String, schema:ISchema):void {
			if (!hasSchema(id)) {
				_schemaRegistry.register(id, schema);
			}
		}
		
		private function parseXML(configuration:XML):void {
			if (configuration.name() != "siam-configuration") 
				throw new RuntimeError("The configuration must be started with the tag <siam-configuration>");
			
			// Parsing Formatting section
			var formatting:XMLList = configuration.child("formatting");
			_formattingManager = new FormattingManagerFactory(formatting[0]).newInstance();
			
			// Parsing Rendering section
			var rendering:XMLList = configuration.child("rendering");
			_renderingManager = new RenderingManagerFactory(rendering[0]).newInstance();
			
			// Parsing each mapping
			var mappingXML:XMLList = configuration.child("mapping");
			_schemaRegistry = new SchemaRegistry(); // Reset registry
			parseMappingXML(mappingXML[0]);
		}
		
		private function parseMappingXML(configuration:XML):void {
			if (!configuration)
				return;

			// iterates thru schemas
			for each (var schemaXML:XML in configuration["schema"]) {
				var schema:ISchema = new SchemaFactory(schemaXML).newInstance();
				var schemaId:String = schema.id;
				if (!schemaId) {
					throw new RuntimeError("A schema must have a valid id");
				} else if (_schemaRegistry.has(schemaId)) {
					throw new RuntimeError("Duplicated schema id '%s'", schemaId);
				}
				// validates schema properties
				for each (var propertyId:String in schema.propertyIds) {
					var property:ISchemaProperty = schema.findProperty(propertyId);
					var formatterId:String = property.formatterId;
					if (StringUtils.isEmpty(formatterId)
						|| (formatterId && !formatting.hasFormatter(formatterId))) {
						throw new RuntimeError("Unknown formatter '%s' for property '%s' under schema '%s'",
											   formatterId, propertyId, schemaId);
					}
					var rendererId:String = property.rendererId;
					if (StringUtils.isEmpty(rendererId)
						|| (rendererId && !rendering.hasRenderer(rendererId))) {
						throw new RuntimeError("Unknown renderer '%s' for property '%s' under schema '%s'",
											   rendererId, propertyId, schemaId);
					}
				}
				registerSchema(schemaId, schema);
			}	
		}
	}
}